/*
	Copyright (C) 2010 2011 Rémi Thebault

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <QtAlgorithms>
#include <QDebug>

#include <procycle/macrotask.h>
#include <procycle/atomtask.h>
#include <procycle/cycle.h>


namespace PC {


	MacroTask::MacroTask(Cycle* cycle, MacroTask* parent)
	:	Task(cycle, parent)
	{
		m_cycle->connectTask(this);
	}

	MacroTask::MacroTask(AtomTask* at)
	:	Task(at)
	{
		m_cycle->connectTask(this);
	}

	MacroTask::~MacroTask() {
		qDeleteAll(m_tasks);

		m_cycle->disconnectTask(this);
	}




	int MacroTask::lastNum() const {
		if (m_tasks.size() == 0) {
			return num();
		}
		else return m_tasks.last()->lastNum();
	}



	int
	MacroTask::setNum(int current) {
		m_num = current;
		current++;
		foreach(Task* t, m_tasks) {
			current = t->setNum(current);
		}
		return current;
	}


	Task*
	MacroTask::resolvTask(QString r_tbsId) const {
		bool ok;
		int point = r_tbsId.indexOf('.');
		if (point == -1) {
			int pos = r_tbsId.toInt(&ok);
			if (!ok || pos < 0 || pos >= m_tasks.size()) {
				throw TaskError();
			}
			return m_tasks.at(pos);
		}
		QString befPoint = r_tbsId.left(point);
		QString aftPoint = r_tbsId.right(r_tbsId.length() - (point+1));
		int pos = befPoint.toInt(&ok);
		if (!ok || pos < 0 || pos >= m_tasks.size()) {
			throw TaskError();
		}
		MacroTask* child = dynamic_cast<MacroTask*>(m_tasks.at(pos));
		if (!child) {
			// here child must be a MacroTask because still a point with children behind
			qDebug() << "task error";
			throw TaskError();
		}
		return child->resolvTask(aftPoint);
	}





	Task*
	MacroTask::createAndInsertTask(int pos) {
		if (pos < 0 || pos > m_tasks.size()) {
			qDebug() << "task error";
			throw TaskError();
		}
		Task* t = new AtomTask(m_cycle, this);
		m_tasks.insert(pos, t);
		emit taskCreated(this, t);
		m_cycle->schedule();
		return t;
	}

	Task*
	MacroTask::createAndAppendTask() {
		Task* t = new AtomTask(m_cycle, this);
		m_tasks.append(t);
		emit taskCreated (this, t);
		m_cycle->schedule();
		return t;
	}

	void
	MacroTask::deleteTask(int pos) {
		if (pos < 0 || pos >= m_tasks.size()) {
			qDebug() << "task error";
			throw TaskError();
		}
		Task* t = m_tasks.at(pos);
		for(Link::iterator it=t->inLinkBegin(); it!=t->inLinkEnd(); it++) {
			m_cycle->deleteLink(*it);
		}
		for(Link::iterator it=t->outLinkBegin(); it!=t->outLinkEnd(); it++) {
			m_cycle->deleteLink(*it);
		}
		emit taskWillDelete(t);
		m_tasks.removeAt(pos);
		delete t;
		m_cycle->schedule();
	}


	void
	MacroTask::insertTask(Task* t, int pos) {
		if (pos < 0 || pos > m_tasks.size()) {
			qDebug() << "task error";
			throw TaskError();
		}
		t->setParent(this);
		m_tasks.insert(pos, t);
		m_cycle->schedule();
	}
	void
	MacroTask::appendTask(Task* t) {
		t->setParent(this);
		m_tasks.append(t);
		m_cycle->schedule();
	}

	Task*
	MacroTask::removeTask(int pos) {
		if (pos < 0 || pos >= m_tasks.size()) {
			qDebug() << "task error";
			throw TaskError();
		}
		Task* t = m_tasks.at(pos);
		m_tasks.removeAt(pos);
		return t;
	}



	void MacroTask::preSchedule() {
		Task::preSchedule();
		foreach(Task* t, m_tasks) {
			t->preSchedule();
		}
	}


	void MacroTask::forwSchedule(Link* inLink) {
		if (inLink != 0) {
			switch(inLink->type()) {
			case Link::TYPE_FS:
			case Link::TYPE_SS:
				m_fTmps.append(inLink->timeTo());
				break;
			case Link::TYPE_SF:
			case Link::TYPE_FF:
				m_fTmps.append(parent()->start());
				break;
			}
			m_start = maxFTmp();
		}
		else if (isBegin()) {
			m_start = parent()->start();
		}


		QList<Task*> begins = beginTasks();
		foreach(Task* t, begins) {
			t->forwSchedule(0);
		}
		foreach(Task* t, m_tasks) {
			if (t->finish() > m_finish) {
				m_finish = t->finish();
			}
		}
		m_length = m_finish - m_start;


		foreach(Link* l, m_outLinks) {
			l->taskTo()->forwSchedule(l);
		}
	}


	void MacroTask::backwSchedule(Link* outLink) {
		if (outLink != 0) {
			switch(outLink->type()) {
			case Link::TYPE_FS:
			case Link::TYPE_FF:
				m_bTmps.append(outLink->lateTimeFrom());
				break;
			case Link::TYPE_SS:
			case Link::TYPE_SF:
				m_bTmps.append(parent()->lateFinish());
				break;
			}
			m_lateFinish = minBTmp();
		}
		else if (isEnd()) {
			m_lateFinish = parent()->lateFinish();
		}


		m_lateStart = m_lateFinish;
		QList<Task*> ends = endTasks();
		foreach(Task* t, ends) {
			t->backwSchedule(0);
		}
		foreach(Task* t, m_tasks) {
			if (t->lateStart() < m_lateStart) {
				m_lateStart = t->lateStart();
			}
		}

		foreach(Link* l, m_inLinks) {
			l->taskFrom()->backwSchedule(l);
		}
	}

	void MacroTask::postSchedule() {
		Task::postSchedule();
		foreach(Task* t, m_tasks) {
			t->postSchedule();
		}
	}


	QList<Task*> MacroTask::beginTasks() {
		QList<Task*> list;
		foreach(Task* t, m_tasks) {
			if (t->isBegin()) {
				list.append(t);
			}
		}
		return list;
	}


	QList<Task*> MacroTask::endTasks() {
		QList<Task*> list;
		foreach(Task* t, m_tasks) {
			if (t->isEnd()) {
				list.append(t);
			}
		}
		return list;
	}






	void
	MacroTask::indentTask(int pos) {
		if (!canIndentTask(pos)) {
			qDebug() << "task error";
			throw TaskError();
		}
		int parentPos = pos-1;
		Task* parent = m_tasks.at(parentPos);
		Task* child = m_tasks.at(pos);
		MacroTask* mt = dynamic_cast<MacroTask*>(parent);
		if (!mt) {
			AtomTask* at = dynamic_cast<AtomTask*>(parent);
			if (at) {
				mt = new MacroTask(at);
				m_tasks.replace(parentPos, mt);
				delete at;
				emit taskTransformed (this, parentPos);
			}
			else {
				qDebug() << "task error";
				throw TaskError();
			}
		}
		mt->appendTask(child);
		m_tasks.removeAt(pos);
		emit taskMoved (mt, this,  child->pos(), pos);
		m_cycle->schedule();
	}


	void
	MacroTask::unindentTask(int childPos, int childChildPos) {
		if (!canUnindentTask(childPos, childChildPos)) {
			qDebug() << "task error";
			throw TaskError();
		}
		if (MacroTask* child = dynamic_cast<MacroTask*>(m_tasks.at(childPos))) {
			int length = child->length();
			Task* childChild = child->removeTask(childChildPos);
			insertTask(childChild, childPos+1);
			emit taskMoved(this, child, childPos+1, childChildPos);
			if (child->taskCount() == 0) {
				removeTask(childPos);
				AtomTask* newChild = new AtomTask(child);
				newChild->setLength(length);
				insertTask(newChild, childPos);
				emit taskTransformed(this, childPos);
				delete child;
			}
			m_cycle->schedule();
		}
	}


	Task* MacroTask::createTaskFromElement(const QDomElement &element) {
		Task* t = 0;
		QDomNodeList nl = element.elementsByTagName("task");
		if (nl.size() > 0) {
			t = new MacroTask(m_cycle, this);
		}
		else {
			t = new AtomTask(m_cycle, this);
		}
		t->loadFromElement(element);
		return t;
	}


	void
	MacroTask::loadFromElement(const QDomElement& element) {
		Task::loadFromElement(element);
		m_tasks.clear();
		QList<QDomElement> el = directElementsByTagName(element, "task");
		foreach(QDomElement e, el) {
			m_tasks.append(createTaskFromElement(e));
		}
	}


	QDomElement
	MacroTask::saveToElement(QDomDocument& doc) const {
		QDomElement element = Task::saveToElement(doc);
		foreach (Task* t, m_tasks) {
			element.appendChild(t->saveToElement(doc));
		}
		return element;
	}


}
